#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <pthread.h>
#include <gtk/gtk.h>
#include <string.h>
#include <errno.h>

#define BUFNUM 10    //缓冲区个数
#define BUFSIZE 1024 //一个缓冲区大小
#define true 1

int shm_id;                                       //共享内存id
int sem_id;                                       //信号量id
unsigned long file_size = 1, all_read_length = 0; //文件大小、已读取字节数
union arg {
    int val;
} sem_num;

typedef struct _myBuf
{
    int in, out, length[BUFNUM];
    char buf[BUFNUM][BUFSIZE];
} myBuf;

//P操作
void P(int sem_id, int index)
{
    struct sembuf mybuf;
    mybuf.sem_num = index;
    mybuf.sem_op = -1;
    mybuf.sem_flg = 0;
    semop(sem_id, &mybuf, 1);
}

//V操作
void V(int sem_id, int index)
{
    struct sembuf mybuf;
    mybuf.sem_num = index;
    mybuf.sem_op = 1;
    mybuf.sem_flg = 0;
    semop(sem_id, &mybuf, 1);
}

unsigned long get_file_name_size(char *file_name)
{
    int fifo_pipe;
    if (mkfifo("fifo1", 0666) < 0 && errno != EEXIST) //创建命名管道
    {
        perror("Create FIFO error");
        exit(-1);
    }
    if ((fifo_pipe = open("fifo1", O_RDONLY)) == -1) //打开fifo1命名管道
    {
        perror("Open FIFO error");
        exit(-1);
    }

    if (read(fifo_pipe, file_name, 1024) == 0)
    {
        perror("Read FIFO error");
        exit(-1);
    }
    printf("已获取文件路径: %s\n", file_name);
    close(fifo_pipe);
    struct stat statbuf;               //文件状态结构体
    if (stat(file_name, &statbuf) < 0) //获取文件大小
        return -1;
    else
        return statbuf.st_size;
}

gint progress_timeout(gpointer progress)
{
    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0 * all_read_length / file_size);
    return TRUE;
}

void *work_thread(void *x)
{
    char file_name[1024];
    file_size = get_file_name_size(file_name);

    shm_id = shmget(233, BUFSIZE * BUFNUM + (2 + BUFNUM) * sizeof(int), IPC_CREAT | 0666);
    sem_id = semget(233, 2, IPC_CREAT | 0666);
    //映射共享内存到子进程 2
    myBuf *buffer = (myBuf *)shmat(shm_id, NULL, SHM_R | SHM_W);

    int out_file; //创建输出文件
    if ((out_file = open("out", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)) == -1)
    {
        perror("Create out file error");
        exit(-1);
    }
    printf("创建输出文件\n");
    while (true)
    {
        P(sem_id, 1); //P操作 1号信号灯，表示缓冲区被读取1次
        if (write(out_file, buffer->buf[buffer->out], buffer->length[buffer->out]) == -1)
        {
            perror("Write to out file error");
            exit(-1);
        }
        all_read_length += buffer->length[buffer->out];
        printf("get from -> %d -> %d\n", buffer->out, buffer->length[buffer->out]);
        if (buffer->length[buffer->out] < BUFSIZE)
        {
            close(out_file);
            printf("输出文件关闭，文件拷贝完成\n");
            break;
        }
        buffer->out = (buffer->out + 1) % BUFNUM;
        V(sem_id, 0); //V操作 0号信号灯，表示缓冲区可写数目加一
    }
}

gint quit(pthread_t *p_id)
{
    gtk_main_quit();
    exit(-1);
}

int main(int argc, char *argv[])
{
    GtkWidget *window;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);              //创建窗口
    gtk_widget_set_uposition(GTK_WIDGET(window), 0, 330);      //设置窗口位置
    gtk_window_set_title(GTK_WINDOW(window), "Read progress"); //设置窗口标题
    gtk_container_set_border_width(GTK_CONTAINER(window), 20); //设置窗口宽度

    GtkWidget *vbox = gtk_vbox_new(FALSE, 10);               //创建纵向组件盒
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 50); //设置组件盒宽度
    gtk_container_add(GTK_CONTAINER(window), vbox);          //将组件盒放入窗口
    gtk_widget_show(vbox);                                   //显示组件盒

    GtkWidget *label;
    char temp_label[1024];
    sprintf(temp_label, "PID: %d", getpid());                   //组装标签
    label = gtk_label_new(temp_label);                          //生成标签
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 10); //将标签放入组件盒
    gtk_widget_show(label);                                     //显示label

    sprintf(temp_label, "Father's PID: %d", getppid());         //组装标签
    label = gtk_label_new(temp_label);                          //生成标签
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 20); //将标签放入组件盒
    gtk_widget_show(label);                                     //显示label

    GtkWidget *progress = gtk_progress_bar_new();                   //创建进度条
    gtk_container_add(GTK_CONTAINER(vbox), progress);               //将progress加入到vbox中
    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0); //设置进度条初值
    gtk_widget_show(progress);                                      //显示进度条
    gtk_timeout_add(100, progress_timeout, progress);               //设置定时器更新进度条

    pthread_t thread_id;
    pthread_create(&thread_id, NULL, work_thread, NULL); //创建工作线程

    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(quit), &thread_id); //绑定线程、gtk_main退出信号
    gtk_widget_show(window);
    gtk_main();

    pthread_join(thread_id, NULL);
    return 0;
}